"""
IsoGame demo game.

The code is split into four main parts:
Model       The models driving the game, physics engine, map loader, etc.
View        The views used to display the game, isometric, debug, etc.
Controller  The controllers used to change the state of the game.
Event       The events used to communicate between the different components.
"""

__author__="jonathan"
__date__ ="$16-May-2010 17:35:02$"

# Import checks
try:
    import pygame
    from pygame.locals import *
    import rabbyt
except ImportError, e:
    print "Required import not found: ", e
    system.exit()

from view.isometric import IsometricView
from view.utils import calc_angle, view_boundary
from model.engine import GameEngine
from view.sprites import IsoSprite, FixedSprite
from view.map import SquareLevelCreator, FileLevelCreator
from math import radians, degrees
from os.path import join as join_path
from collections import defaultdict

# ------------------------------------------------------------------------------
# pygame & rabbyt initialisation.
# ------------------------------------------------------------------------------
pygame.init()
screen_size = 800, 600
screen = pygame.display.set_mode(screen_size, pygame.OPENGL|pygame.DOUBLEBUF)
screen_rect = screen.get_rect()
rabbyt.set_viewport(screen_rect, screen_rect)
rabbyt.set_default_attribs()
clock = pygame.time.Clock()
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# Create the view.
# ------------------------------------------------------------------------------
iso_view = IsometricView(tilt=radians(60), offset=(screen_rect.center + (0,)))

# ------------------------------------------------------------------------------
# Create the level as a 2D linked grid for efficient iteration.
# ------------------------------------------------------------------------------
#
# Uncomment below for different levels
#
#levelCreator = SquareLevelCreator(join_path('walls', 'floor0005'), 100, (105, 105))
#levelCreator = FileLevelCreator("level1.lvl")
levelCreator = FileLevelCreator("level2.lvl")
grid = levelCreator.create_grid()

# ------------------------------------------------------------------------------
# Creating the game objects.
# ------------------------------------------------------------------------------
arrow = IsoSprite('arrow', (0,0,0))
pointer = rabbyt.Sprite(join_path('images', 'pointer.png'))
#box_size = 10
#box = FixedSprite('box', (box_size, box_size), (400, 600, 0), 0)
rockets = {}
rocket_count = 0
# pre cache a rocket
IsoSprite('rocket', (0,0,0))
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# Create the model.
# ------------------------------------------------------------------------------
game_objects = {}
game_objects['arrow'] = {'start_pos': (0,0,0)}
model = GameEngine(game_objects)
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# Set up panning.
# ------------------------------------------------------------------------------
PAN_BORDER_WIDTH = 0.1      # relative width of the border
PAN_SPEED = 5               # speed to pan the screen at
PAN_ENABLED = False         # whether to pan or track player
panning = 0                 # the direction we are panning

# Calculate edge of screen areas to use for panning.
width, height = screen_rect.size
left_pan_rect = pygame.Rect(0, 0, PAN_BORDER_WIDTH * width, height)
right_pan_rect = pygame.Rect((1-PAN_BORDER_WIDTH) * width, 0, PAN_BORDER_WIDTH * width, height)
top_pan_rect = pygame.Rect(0, 0, width, PAN_BORDER_WIDTH * height)
bottom_pan_rect = pygame.Rect(0, (1-PAN_BORDER_WIDTH) * height, width, PAN_BORDER_WIDTH * height)

PAN_LEFT = 1
PAN_RIGHT = 2
PAN_UP = 4
PAN_DOWN = 8
def get_panning_value(panning):
    x, y = 0, 0
    if panning & PAN_LEFT: x += PAN_SPEED
    if panning & PAN_RIGHT: x -= PAN_SPEED
    if panning & PAN_UP: y += PAN_SPEED
    if panning & PAN_DOWN: y -= PAN_SPEED
    return (x, y, 0)
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# The main loop.
# ------------------------------------------------------------------------------
running = True

while running:

    # Clear the screen.
    rabbyt.clear((0.0, 0.25, 0.0))

    # Update the sprites if the are on screen.
    iso_screen = view_boundary(iso_view, screen_rect, levelCreator.tile_size)

    # The tiles which we need to render this loop.
    to_render = defaultdict(lambda: [])

    # Define the update function and filters to apply to the grid.
    def func(tile):
        tile.update(iso_view)
        to_render[tile.zorder].append(tile)
        #tile.render
    def set_center(tile):
        rect = pygame.Rect(tile.pos[:2], tile.rect[2:])
        return rect.collidepoint(iso_screen.center)
    def filter(tile):
        return iso_screen.collidepoint(tile.pos[:2])

    # Iterate through the grid applying the update function.
    grid.apply(func, filter, set_center)

    # Render the box
#    if iso_screen.collidepoint(box.pos[:2]):
#        box.update(iso_view)
#        to_render[0].append(box)

    clock.tick()

    pygame.display.set_caption('angle=%d tilt=%d fps=%d tiles=%d panning=%s' % (
        degrees(iso_view.angle),
        degrees(iso_view.tilt),
        clock.get_fps(),
        levelCreator.num_tiles,
        PAN_ENABLED
    ))

    # Main event loop
    events = pygame.event.get()
    for event in events:

        if event.type == QUIT:
            running = False

        if event.type == KEYDOWN:
            if event.key == K_ESCAPE:
                running = False
            if event.key == K_SPACE:
                model.jump('arrow')
            if event.key == K_p:
                iso_view.offset = (screen_rect.center + (0,))
                PAN_ENABLED = not PAN_ENABLED
            if event.key == K_w:
                panning |= PAN_UP
            if event.key == K_s:
                panning |= PAN_DOWN
            if event.key == K_a:
                panning |= PAN_LEFT
            if event.key == K_d:
                panning |= PAN_RIGHT
            # firing rockets!
            if event.key == K_x:
                rocket = IsoSprite('rocket', arrow.pos)
                model.add_object(
                    'rocket%i' % rocket_count,
                    {'start_pos': model.get_pos('arrow'),
                     'start_vel': 500 * model.get_dir('arrow'),
                     'direction': model.get_dir('arrow')})
                rockets[rocket_count] = (rocket)
                rocket_count += 1

        if event.type == KEYUP:
            if event.key == K_w:
                panning ^= PAN_UP
            if event.key == K_s:
                panning ^= PAN_DOWN
            if event.key == K_a:
                panning ^= PAN_LEFT
            if event.key == K_d:
                panning ^= PAN_RIGHT

        # Set the player target to the mouseclick
        if event.type == MOUSEBUTTONDOWN:
            model.set_target('arrow', iso_view.from_view((event.pos + (0,),))[0])

        # If the mouse is at the side of the screen then pan
        if event.type == MOUSEMOTION:
            pos = event.pos
            if left_pan_rect.collidepoint(pos):
                panning |= PAN_LEFT
            else:
                if panning & PAN_LEFT: panning ^= PAN_LEFT
            if right_pan_rect.collidepoint(pos):
                panning |= PAN_RIGHT
            else:
                if panning & PAN_RIGHT: panning ^= PAN_RIGHT
            if top_pan_rect.collidepoint(pos):
                panning |= PAN_UP
            else:
                if panning & PAN_UP: panning ^= PAN_UP
            if bottom_pan_rect.collidepoint(pos):
                panning |= PAN_DOWN
            else:
                if panning & PAN_DOWN: panning ^= PAN_DOWN

    if PAN_ENABLED:
        if panning:
            iso_view.offset += get_panning_value(panning)
    else:
        iso_view.center = arrow.pos

    model.update_all(None, None)

    # Draw player arrow
    arrow.pos = model.get_pos('arrow')
    xy = iso_view.to_view((model.get_pos('arrow'),))[0]
    dir = iso_view.to_view((model.get_pos('arrow') + model.get_dir('arrow'),))[0]
    arrow.angle = calc_angle(xy, dir)

    arrow.update(iso_view)
    to_render[0].append(arrow)
    #arrow.render()

    del_ids = []
    for id, rocket in rockets.iteritems():
        rocket_name = 'rocket%i' % id
        rocket.pos = model.get_pos(rocket_name)
        if not iso_screen.collidepoint(rocket.pos[:2]):
            del_ids.append(id)
        xy = iso_view.to_view((model.get_pos(rocket_name),))[0]
        dir = iso_view.to_view((model.get_pos(rocket_name) + model.get_dir(rocket_name),))[0]
        rocket.angle = calc_angle(xy, dir) + 90
        rocket.update(iso_view)
        to_render[0].append(rocket)

    # delete any rockets that have gone off screen
    for id in del_ids:
        del rockets[id]

    # Draw target pointer
    if not model.at_target('arrow'):
        pointer.xy = iso_view.to_view((model.get_target('arrow'),))[0][:2]
        to_render[0].append(pointer)
        #pointer.render()

    # Render the sprites in z-order
    for key in sorted(to_render.keys()):
        for sprite in to_render[key]:
            sprite.render()

    pygame.display.flip()
